import type { Agent } from "./Agent";
import { World } from "./WorldBase";
import type { Descriptor, Direction, DestinationData } from "./Definitions";
import { AZone } from "./AZone";
import * as Debug from "./Debug";
import { AObject } from "./AObject";
import { Sentence, type Referent, type SyntaxElement, type SentenceContent, Signifier, N, HistoryEntry, type DurativeAspects, type HistoryEntryType } from "./NarrativeBase";

function reportPunctual(
    type: HistoryEntryType, ag: AObject | Agent | undefined, 
    elem: (SyntaxElement | Referent)[], debug: string = '', group = '') 
{
    let s = Array.isArray(elem) ? new Sentence(elem) : elem;
    s.group = group;
    return new HistoryEntry(ag, type, s, debug);
}

function reportDurative(
    type: HistoryEntryType, ag: AObject | Agent | undefined, 
    aspects: DurativeAspects, debug: string = '', group = '') 
{
    for (const key in aspects)
        aspects[key as keyof DurativeAspects]!.group = group;
    return new HistoryEntry(ag, type, aspects, debug);
}

export const U = {
    choice: <T>(...args: T[]) => args[Math.floor(Math.random() * args.length)],

    action: reportPunctual.bind(undefined, 'action'),
    thought: reportPunctual.bind(undefined, 'thought'),
    meta: reportPunctual.bind(undefined, 'meta'),
    annotation: reportPunctual.bind(undefined, 'annotation'),

    metaText: (ag: Agent | AObject, ...elem: SentenceContent) => 
        reportPunctual('meta', ag, N.text(...elem)),

    annotationText: (ag: Agent | AObject, ...elem: SentenceContent) => 
        reportPunctual('annotation', ag, N.text(...elem)),


    actionDurative: reportDurative.bind(undefined, 'action'),
    thoughtDurative: reportDurative.bind(undefined, 'thought'),

    timeString(t = World.schedule.absoluteTime) {
        let h = Math.floor(t / 3600);
        let m = Math.floor(t / 60) % 60;
        let s = Math.floor(t % 60);
        return h.toString() +
            ':' + m.toString().padStart(2, '0') +
            ':' + s.toString().padStart(2, '0');
    },

    desc: <T>(desc: SentenceContent, 
        ldesc?: SentenceContent, pldesc?: SentenceContent): Descriptor<T> => 
    {
        let ld = ldesc ?? desc;
        let pld = pldesc ?? ld;
        return (_, ag) => N.text(...(
            ag.hasLight ? (ag.parentZone.isLit ? ld : pld) : desc));
    },

    lambdaDesc: (ag: Agent, desc: SentenceContent, 
        ldesc?: SentenceContent, pldesc?: SentenceContent): SentenceContent => 
    {
        let ld = ldesc ?? desc;
        let pld = pldesc ?? ld;
        return N.text(...(ag.hasLight ? (ag.parentZone.isLit ? ld : pld) : desc));
    },
    
    createZone: (name: string, alias: string[], 
        dests: () => {[k in Direction]?: DestinationData}, 
        args: Partial<AZone>) => new AZone(name, alias, {
            passages: (dir) => dests()[dir],
            ...args
        }),

    groupPrototype(objects: AObject[]) {
        let map = new Map<AObject | null, AObject[]>;
        for (let obj of objects)
            if (map.has(obj.prototype))
                map.get(obj.prototype)!.push(obj);
            else
                map.set(obj.prototype, [obj]);
        return map;
    },

    groupPrototypeSignifier(sigs: Signifier[]) {
        let map = new Map<AObject | null, Signifier[]>;
        for (let sig of sigs) {
            let key: AObject | null = null;
            if (sig.object instanceof AObject)
                key = sig.object.prototype;
            if (map.has(key))
                map.get(key)!.push(sig);
            else
                map.set(key, [sig]);
        }
        return map;
    }
};
